Electron 系统托盘图标及功能开发
概述
Electron 应用运行后,在 macOS 状态栏或 Windows 系统托盘中默认不会显示应用图标。本节介绍如何使用 Electron 的 Tray 模块创建系统托盘图标,实现类似 QQ/微信的托盘菜单交互,包括图标显示、上下文菜单、点击事件处理等功能。
Tray 模块核心概念
跨平台托盘位置
| 平台 | 托盘位置 | 交互方式 |
|---|---|---|
| macOS | 屏幕右上角状态栏 | 左键点击/右键菜单 |
| Windows | 右下角系统托盘 | 左键点击/右键菜单 |
| Linux | 系统面板通知区域 | 左键点击/右键菜单 |
核心 API 对照
| API | 用途 | 说明 |
|---|---|---|
Tray | 创建托盘图标 | 必须在 app.whenReady() 之后实例化 |
nativeImage | 创建原生图标 | 支持 PNG、ICO 等格式 |
Menu.buildFromTemplate | 构建上下文菜单 | 右键/点击弹出的菜单项 |
tray.setContextMenu | 设置右键菜单 | 绑定菜单到托盘图标 |
tray.setToolTip | 设置悬浮提示 | 鼠标悬停时显示文字 |
完整实现代码
基础托盘图标创建
// main/tray.ts
import { app, Tray, Menu, nativeImage, BrowserWindow } from 'electron'
import path from 'node:path'
let tray: Tray | null = null
export function createTray(): void {
// macOS 使用 Template 图标(自动适配明暗模式)
const iconPath = path.join(__dirname, '../assets/trayIconTemplate.png')
const icon = nativeImage.createFromPath(iconPath)
// 实例化 Tray(必须在 app.whenReady 之后)
tray = new Tray(icon)
// 设置悬浮提示
tray.setToolTip('toimc-tools')
// 构建上下文菜单
const contextMenu = Menu.buildFromTemplate([
{
label: '显示主窗口',
click: () => {
const win = BrowserWindow.getAllWindows()[0]
if (win) {
win.isVisible() ? win.show() : win.hide()
}
}
},
{ type: 'separator' },
{
label: '偏好设置',
click: () => {
// 打开设置页面
const win = BrowserWindow.getAllWindows()[0]
if (win) {
win.webContents.send('navigate', '/settings')
win.show()
}
}
},
{ type: 'separator' },
{
label: '退出',
role: 'quit'
}
])
tray.setContextMenu(contextMenu)
// macOS: 左键点击显示窗口
tray.on('click', () => {
const win = BrowserWindow.getAllWindows()[0]
if (win) {
win.isVisible() ? win.hide() : win.show()
}
})
}
export function destroyTray(): void {
if (tray) {
tray.destroy()
tray = null
}
}
typescript
在主进程入口集成
// main/index.ts
import { app, BrowserWindow } from 'electron'
import { createTray, destroyTray } from './tray'
app.whenReady().then(() => {
createMainWindow()
createTray() // 创建托盘图标
})
// 防止 macOS 下关闭所有窗口时退出应用
app.on('window-all-closed', (e) => {
if (process.platform === 'darwin') {
e.preventDefault()
}
})
app.on('before-quit', () => {
destroyTray()
})
typescript
动态托盘图标(带未读消息数)
// main/trayBadge.ts
import { Tray, nativeImage } from 'electron'
import path from 'node:path'
/**
* 在托盘图标上叠加未读消息数字
* macOS 使用 dock badge,Windows/Linux 使用图标叠加
*/
export function updateTrayBadge(tray: Tray, count: number): void {
if (process.platform === 'darwin') {
// macOS: 使用 Dock Badge
app.dock.setBadge(count > 0 ? String(count) : '')
} else {
// Windows/Linux: 生成带数字的图标
if (count > 0) {
const icon = nativeImage.createFromPath(
path.join(__dirname, '../assets/trayIconBadge.png')
)
tray.setImage(icon)
} else {
const icon = nativeImage.createFromPath(
path.join(__dirname, '../assets/trayIconTemplate.png')
)
tray.setImage(icon)
}
}
}
typescript
图标资源规范
| 平台 | 推荐尺寸 | 格式 | 命名规范 |
|---|---|---|---|
| macOS | 16x16 / 32x32 (@2x) | PNG (Template) | trayIconTemplate.png |
| Windows | 16x16 / 32x32 | ICO / PNG | trayIcon.ico |
| Linux | 16x16 / 32x32 | PNG | trayIcon.png |
macOS 的 Template 图标(文件名以 Template 结尾)会自动适配系统明暗模式。
实践要点
Tray实例必须存储在外层变量中,避免被垃圾回收导致图标消失- 必须在
app.whenReady()之后才能创建Tray实例 - macOS 下使用 Template 图标命名约定,系统自动处理明暗模式适配
- 关闭所有窗口后,macOS 应阻止默认退出行为(用户通过托盘操作应用)
- 应用退出前调用
tray.destroy()清理托盘资源 - Windows 平台可通过
tray.displayBalloon()显示系统通知气泡
↑